package com.stark.wechat.util;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.text.ParseException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;

import com.alibaba.druid.filter.config.ConfigTools;

/**
 * 公共方法集合。
 * @author Ben
 * @since 1.0.0
 * @version 1.0.0, 2017/8/5
 */
public class Tools {
	/**
	 * 获得一个 UUID 。
	 * @return uuid 。
	 */
	public static String getUUID() {
		String uuid = UUID.randomUUID().toString();
		return uuid;
	}

	/**
	 * 将为 null 的 String 转换成 "" 。
	 * @param str 字符串。
	 * @return str 为 null 时返回 "" ， 否则返回 str.trim() 。
	 */
	public static String nullToBlank(String str) {
		str = StringUtils.isBlank(str) ? "" : str.trim();
		return str;
	}
	
	/**
	 * 将字节码编码成 base64 的字符串，并去掉空格、回车换行。
	 * @param bytes 字节码。
	 * @return base64 编码、并去掉空格和回车符的字符串。
	 * @see org.apache.commons.codec.binary.Base64#encodeBase64String(byte[])
	 */
	public static String base64Encode(byte[] bytes) {
		String str = Base64.encodeBase64String(bytes);
		str = str.replaceAll(" ", "").replaceAll("\r\n", "");
		return str;
	}
	
	/**
	 * 组织 base64 编码的图片字符串。
	 * <p>将该方法所得的字符串置成 &ltimg> 标签的 src ， 即可显示图片。
	 * @param bytes 图片字节码。
	 * @return base64 编码的图片字符串。
	 * @see #base64Encode(byte[])
	 */
	public static String generateImgBase64Str(byte[] bytes) {
		if (bytes == null || bytes.length == 0) {
			return "";
		}
		String imgBase64Str = "data:image/jpg;base64," + Tools.base64Encode(bytes);
		return imgBase64Str;
	}
	
	/**
	 * 组织 base64 编码的图片字符串。
	 * <p>将该方法所得的字符串置成 &ltimg> 标签的 src ， 即可显示图片。
	 * @param filename 图片路径。
	 * @return base64 编码的图片字符串。
	 * @throws IOException 读取照片时抛出异常。
	 * @see #generateImgBase64Str(byte[])
	 */
	public static String generateImgBase64Str(String filename) throws IOException {
		byte[] bytes = FileUtils.readFileToByteArray(new File(filename));
		String imgBase64Str = Tools.generateImgBase64Str(bytes);
		return imgBase64Str;
	}
	
	/**
	 * 解析 base64 编码的图片字符串获得字节码。
	 * @param imgBase64Str base64 编码的图片字符串。
	 * @return 图片字节码。
	 * @see org.apache.commons.codec.binary.Base64#decodeBase64(String)
	 */
	public static byte[] getBytesFromImgBase64Str(String imgBase64Str) {
		imgBase64Str = imgBase64Str.substring(imgBase64Str.indexOf("base64,") + "base64,".length());
		byte[] bytes = org.apache.commons.codec.binary.Base64.decodeBase64(imgBase64Str);
		return bytes;
	}
	
	/**
	 * 获取客户端 ip 地址。
	 * @param request HTTP 请求对象。
	 * @return 客户端 ip 地址。
	 */
	public static String getClientIp(HttpServletRequest request) {
		String ip = request.getHeader("x-forwarded-for");
		if (StringUtils.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getHeader("Proxy-Client-IP");
		}
		if (StringUtils.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getHeader("WL-Proxy-Client-IP");
		}
		if (StringUtils.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getRemoteAddr();
		}
		return ip;
	}
	
	/**
	 * 获得 url 参数 map 。
	 * @param queryString url 查询参数字符串。
	 * @return {@link HashMap} 。
	 */
	public static Map<String, String> getQueryParamMap(String queryString) {
		Map<String, String> map = new HashMap<>();
		if (queryString == null || "".equals(queryString.trim())) {
			return map;
		}
		
		String[] paramArray = queryString.split("&");
		for (String keyValueStr : paramArray) {
			map.put(keyValueStr.substring(0, keyValueStr.indexOf("=")), keyValueStr.substring(keyValueStr.indexOf("=") + 1));
		}
		return map;
	}
	
	/**
	 * 获得 url 参数 map 。
	 * @param request HTTP 请求对象。
	 * @return {@link HashMap} 。
	 * @see #getQueryParamMap(String)
	 */
	public static Map<String, String> getQueryParamMap(HttpServletRequest request) {
		String queryString = request.getQueryString();
		Map<String, String> map = Tools.getQueryParamMap(queryString);
		return map;
	}
	
	/**
	 * 加密 druid 连接池密码。
	 * @param password 密码明文。
	 * @return 加密字符串。
	 * @throws Exception 加密时抛出异常。
	 * @see com.alibaba.druid.filter.config.ConfigTools#encrypt(String)
	 */
	public static String encryptDruidPassword(String password) throws Exception {
		password = ConfigTools.encrypt(password);
		return password;
	}
	
	/**
	 * 解密 druid 连接池密码。
	 * @param password 加密字符串。
	 * @return 解密后的密码明文。
	 * @throws Exception 解密时抛出异常。
	 * @see com.alibaba.druid.filter.config.ConfigTools#decrypt(String)
	 */
	public static String decryptDruidPassword(String password) throws Exception {
		password = ConfigTools.decrypt(password);
		return password;
	}
	
	/**
	 * 获得 Javabean 属性的 get 或 set 方法名。
	 * @param getOrSet 方法类型， "get" 或 "set" 。
	 * @param fieldName 属性名称。
	 * @return 方法名。
	 */
	public static String camelCaseGetterSetter(String getOrSet, String fieldName) {
		if (StringUtils.isBlank(fieldName)) {
			return "";
		}
		
		String funcName;
		if (fieldName.length() <= 1) {
			funcName = getOrSet + fieldName.toUpperCase();
			return funcName;
		}
		
		String secondChar = fieldName.length() <= 2 ? fieldName.substring(1) : fieldName.substring(1, 2);
		if (StringUtils.isAllUpperCase(secondChar)) {
			funcName = getOrSet + fieldName.substring(0, 1) +  fieldName.substring(1);
			return funcName;
		}
		
		funcName = getOrSet + fieldName.substring(0, 1).toUpperCase() +  fieldName.substring(1);
		return funcName;
	}
	
	/**
	 * 获取当前服务器 ip 。
	 * @return 服务器 ip 。 
	 * @throws UnknownHostException 主机无法识别时抛出异常。
	 */
	public static String getServerIp() throws UnknownHostException {
		InetAddress addr = InetAddress.getLocalHost();
		String ip = addr.getHostAddress();
		return ip;
	}
	
	/**
	 * 获取当前服务器主机名。
	 * @return 服务器主机名。
	 * @throws UnknownHostException 主机无法识别时抛出异常。
	 */
	public static String getServerHostName() throws UnknownHostException {
		InetAddress addr = InetAddress.getLocalHost();
		String hostName = addr.getHostName();
	    return hostName;
	}
	
	/**
	 * 在 URL 后拼接查询字符串。
	 * @param url 请求 URL 。
	 * @param queryString 参数字符串。
	 * @return 拼接所得的字符串。
	 */
	public static String concatURLQueryString(String url, String queryString) {
		if (StringUtils.isBlank(url)) {
			return "";
		}
		
		if (StringUtils.isBlank(queryString)) {
			return url;
		}
		
		if (!url.endsWith("?") && !url.endsWith("&")) {
			url += (url.contains("?") ? "&" : "?");
		}
		
		if ((queryString.startsWith("?") || queryString.startsWith("&")) && queryString.length() > 1) {
			queryString = queryString.substring(1);
		}
		
		url += queryString;
		return url;
	}
    
    /**
	 * 获取项目绝对路径，以 {@link java.io.File#separator} 结尾。
	 * @return 项目绝对路径。
	 */
	public static String getAppRealPath(HttpServletRequest request) {
		if (request != null) {
			String appRealPath = request.getSession().getServletContext().getRealPath("") + File.separator;
			return appRealPath;
		}
		
		String appRealPath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
		appRealPath = appRealPath.substring(1, appRealPath.indexOf("WEB-INF"));
		String osName = System.getProperty("os.name").toLowerCase();
		if (osName.contains("mac") || osName.contains("linux")) {
			appRealPath = File.separator + appRealPath;
		}
		return appRealPath;
	}
	
	/**
	 * 将 JSR303 校验错误封装成 {@link HashMap} 。
	 * @param result JSR303 校验结果。
	 * @return 错误 {@link HashMap} ，key 为校验字段，value 为错误描述。
	 */
	public static Map<String, String> getJSR303ErrorMap(BindingResult result) {
		Map<String, String> errorMap = new HashMap<>();
		
		List<FieldError> errorList = result.getFieldErrors();
		for (FieldError error : errorList) {
			errorMap.put(error.getField(), error.getDefaultMessage());
		}
		
		return errorMap;
	}
	
	/**
	 * 判断字符串是否为数值类型。
	 * @param str 字符串。
	 * @return 以下格式返回 true：
	 * <p>"123", "0123", "+123", "-123", "+0123", "-0123"
	 * <p>"0.1", "+0.1", "-0.1"
	 * <p>"0.12%", "+0.12%", "-0.12%"
	 */
	public static boolean isNumeric(String str) {
		if (StringUtils.isBlank(str)) {
			return false;
		}
		
		if (str.endsWith("%")) {
			str = str.substring(0, str.length() - 1);
		}
		
		boolean flag = true;
		try {
			Double.parseDouble(str.trim());
		} catch (Exception e) {
			flag = false;
		}
		return flag;
	}
	
	/**
	 * 字符串转换成数值类型。
	 * @param str 字符串。
	 * @return <p>"123", "0123", "+123", "+0123" 返回 123.0
	 * <p>"-123", "-0123" 返回 -123.0
	 * <p>"0.1", "+0.1" 返回 0.1
	 * <p>"-0.1" 返回 -0.1
	 * <p>"0.12%", "+0.12%" 返回 0.0012
	 * <p>"-0.12%" 返回 -0.0012
	 * @throws ParseException 无法转化数值类型时抛出异常。
	 */
	public static Double numberValueOf(String str) throws ParseException {
		double x = 1;
		if (!StringUtils.isBlank(str) && str.endsWith("%")) {
			str = str.substring(0, str.length() - 1);
			x = 0.01;
		}
		
		Double num = 0d;
		try {
			num = Double.parseDouble(str);
		} catch (Exception e) {
			throw new ParseException(e.getMessage(), 0);
		}
		return num * x;
	}
	
	/**
	 * 获取异常的堆栈跟踪信息。
	 * @param e 异常对象。
	 * @return 堆栈跟踪信息。
	 */
	public static String getExceptionStackTrace(Exception e) {
		StringWriter sw = new StringWriter();
        e.printStackTrace(new PrintWriter(sw));
		String stackTrace = sw.toString().replaceAll("\n", "<br>&emsp;");
		return stackTrace;
	}
}
